﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
using System.IO;
using System.Diagnostics;
using Sustainalytics.AwsS3.Client;
using Sustainalytics.Entities;
using Sustainalytics.Utils.Configuration;
using Sustainalytics.Reporting.Framework;

namespace Sustainalytics.RiskRatings.Reporting
{
    public class ReportsManager
    {
        private readonly int _bulkSize;
        private readonly string _dwConnectionString;
        private readonly string _inputFile;
        private readonly ConcurrentQueue<string> _log;
        private readonly string _databaseGroup;
        private readonly S3ClientSettings _s3ClientSettings;
        private readonly string _bucketName;
        private string _path;
        private int _reportsFullCount;
        private int _reportsSummaryCount;
        private int _reportsCoreFullCount;
        private int _reportsCoreSummaryCount;
        private int _successfullyGeneratedFullReports;
        private int _successfullyGeneratedSummaryReports;
        private int _successfullyGeneratedCoreFullReports;
        private int _successfullyGeneratedCoreSummaryReports;

        private CancellationTokenSource _tokenSource;

        public double Progress { get; set; }

        public string[] Log => _log.ToArray();
        public int ReportsFullCount => _reportsFullCount;
        public int ReportsSummaryCount => _reportsSummaryCount;
        public int ReportsCoreFullCount => _reportsCoreFullCount;
        public int ReportsCoreSummaryCount => _reportsCoreSummaryCount;
        public int SuccessfullyGeneratedFullReports => _successfullyGeneratedFullReports;
        public int SuccessfullyGeneratedSummaryReports => _successfullyGeneratedSummaryReports;
        public int SuccessfullyGenerateCoreFullReports => _successfullyGeneratedCoreFullReports;
        public int SuccessfullyGeneratedCoreSummaryReports => _successfullyGeneratedCoreSummaryReports;

        public ReportsManager()
        {
            _log = new ConcurrentQueue<string>();
        }

        public ReportsManager(int bulkSize, IReadOnlyDictionary<string, string> connectionStrings)
        {
            _bulkSize = bulkSize;
            _log = new ConcurrentQueue<string>();
            _reportsFullCount = _reportsSummaryCount = _reportsCoreFullCount = _reportsCoreSummaryCount =  0;

            if (!connectionStrings.TryGetValue("RiskRatingsResearchDW", out _dwConnectionString))
                throw new ArgumentException("RiskRatingsResearchDW key is missing!");

            _databaseGroup = _dwConnectionString.EndsWith("RiskRatingsResearchDW_A") ? "A" : "B";

            _inputFile = "dev-globalaccess-europe\riskratings_glossary";

            _s3ClientSettings = new S3ClientSettings
            {
                AccessKey = SettingsHelper.Read("accesKeyId", "AKIAJKGZZMOFAVOVLHJA"),
                SecretAccessKey = SettingsHelper.Read("awsSecretKey", "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"),
                ServiceEndPoint = SettingsHelper.Read("s3ServiceEndPoint", "https://s3-eu-west-1.amazonaws.com/")
            };

            _bucketName = SettingsHelper.Read("s3bucket", "dev-globalaccess-europe");
            //_path = $"E:\\{ReportType.RiskRatingsReport}_{_databaseGroup}";

            Console.Write("Load all images from S3 ... ");
            ReportHelper.Initialize();
            Console.WriteLine("done");
        }

        public static ReportsManager GetInstance(int bulkSize, Dictionary<string, string> connectionStrings)
        {
            return new ReportsManager(bulkSize, connectionStrings);
        }


        public void CancelPdfReportsGeneration()
        {
            if (_tokenSource != null)
                _tokenSource.Cancel();

            _log.Enqueue("RiskRating Reports generation was canceled.");
        }

        private bool GenerateCompanyReport(ReportDataSource ds)
        {
            try
            {
                var generator = new CompanyReportGenerator();
                GeneratedPdfReport result;

                if (ds.ReportType == ReportType.CoreRiskRatingsReport || ds.ReportType == ReportType.SummaryCoreRiskRatingsReport)
                {
                    result = generator.GenerateCorePdfReport(ds);
                }
                else
                {
                    result = generator.GeneratePdfReport(ds); 
                }

                if (result.Name == null) return false;

                var pdfReport = new RiskRatingPdfReport
                {
                    Id = result.Id,
                    FileName = result.Name,
                    FileContent = result.PdfDocument,
                    CreationTimestamp = DateTime.UtcNow,
                    Type = ds.ReportType
                };


                switch (ds.ReportType)
                {
                    case ReportType.RiskRatingsReport:
                        {
                            pdfReport.FileName = ReportName.RiskRatings.GetFullReportName(ds.CompanyProfile.CompanyName, result.Id);
                        }break;
                    case ReportType.SummaryRiskRatingsReport:
                        {
                            pdfReport.FileName = ReportName.RiskRatingsSummary.GetFullReportName(ds.CompanyProfile.CompanyName, result.Id);
                        }
                        break;
                    case ReportType.CoreRiskRatingsReport:
                        {
                            pdfReport.FileName = ReportName.CoreRiskRatings.GetFullReportName(ds.CompanyProfile.CompanyName, result.Id);
                        }
                        break;
                    case ReportType.SummaryCoreRiskRatingsReport:
                        {
                            pdfReport.FileName = ReportName.CoreRiskRatingsSummary.GetFullReportName(ds.CompanyProfile.CompanyName, result.Id);
                        }
                        break;
                }

                //SavePdfReport(result.PdfDocument, pdfReport.FileName);

                S3Write(pdfReport);

                switch (ds.ReportType)
                {
                    case ReportType.RiskRatingsReport: { Interlocked.Increment(ref _successfullyGeneratedFullReports); } break;
                    case ReportType.SummaryRiskRatingsReport: { Interlocked.Increment(ref _successfullyGeneratedSummaryReports); } break;
                    case ReportType.CoreRiskRatingsReport: { Interlocked.Increment(ref _successfullyGeneratedCoreFullReports); } break;
                    case ReportType.SummaryCoreRiskRatingsReport: { Interlocked.Increment(ref _successfullyGeneratedCoreSummaryReports); } break;

                }

                _log.Enqueue(
                    $" {DateTime.UtcNow.ToString("T")} :" + $"{ds.CompanyProfile.CompanyName} company {ds.ReportType.ToString()} pdf was generated and saved successfully. " +
                    $"Company ID: {ds.CompanyProfile.Id}" );
                Console.WriteLine(
                    $" {DateTime.UtcNow.ToString("T")} :" + $"{ds.CompanyProfile.CompanyName} company {ds.ReportType.ToString()} pdf was generated and saved successfully. " +
                    $"Company ID: {ds.CompanyProfile.Id}");

                return true;
            }
            catch (Exception ex)
            {
                _log.Enqueue(DateTime.UtcNow.ToString("T")+$":Faild to generate {ds.ReportType.ToString()} pdf for company {ds.CompanyProfile.CompanyName} with ID: {ds.CompanyProfile.Id}. Error: {ex}");
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(DateTime.UtcNow.ToString("T")+$":Faild to generate {ds.ReportType.ToString()} pdf for company {ds.CompanyProfile.CompanyName} with ID: {ds.CompanyProfile.Id}. Error: {ex}");
                Console.ResetColor();
                return false;
            }
        }

        private void S3Write(RiskRatingPdfReport report)
        {
            _path = $"E:\\{report.Type}_{_databaseGroup}";
            var fullPath = Path.Combine(_path, report.FileName);

            var file = $"{Path.GetFileName(Path.GetDirectoryName(fullPath))}/{report.FileName}";

            _log.Enqueue(DateTime.UtcNow.ToString("T")+$":Uploading file {file} in S3 {_bucketName} bucket");

            try
            {
                using (var s3Client = new S3Client(_s3ClientSettings))
                {
                    s3Client.UploadFile(_bucketName, file, report.FileContent);
                }
            }
            catch (Exception ex)
            {
                _log.Enqueue(DateTime.UtcNow.ToString("T")+$":Uploading file {file} in S3 failed. Error:{ex}");
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(DateTime.UtcNow.ToString("T")+$":Uploading file {file} in S3 failed. Error:{ex}");
                Console.ResetColor();
            }
        }
        private void SavePdfReport(byte[] file, string fileName)
        {
            if (!Directory.Exists(@"E:\Reports\"))
                Directory.CreateDirectory(@"E:\Reports\");

            File.WriteAllBytes(string.Format(@"E:\Reports\{0}", @fileName), file);
        }

        private void LoadCompanyReportData(ITargetBlock<ReportDataSource> target)
        {
            var reportDataProvider = new ReportDataProvider(_bulkSize, _tokenSource.Token, _dwConnectionString, _inputFile);
            _successfullyGeneratedFullReports = 0;
            _successfullyGeneratedSummaryReports = 0;
            

            while (true)
            {
                var dataSourceObjects = reportDataProvider.GetCompanyReportData();

                if (dataSourceObjects.Length <= 0) break;

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("{0} companies done, next batch of {1} companies retrieved", _reportsFullCount, dataSourceObjects.Length);
                Console.ResetColor();

                foreach (var ds in dataSourceObjects)
                {
                    target.SendAsync(ds).Wait();

                    var clone = ds.Clone();
                    clone.ReportType = ReportType.SummaryRiskRatingsReport;
                    target.SendAsync(clone).Wait();
                    _reportsSummaryCount++;
                }
                _reportsFullCount += dataSourceObjects.Length;
            }
        }

        private void LoadCompanyReportData_Core(ITargetBlock<ReportDataSource> target)
        {
            var reportDataProvider = new ReportDataProvider(_bulkSize, _tokenSource.Token, _dwConnectionString, _inputFile);
            _successfullyGeneratedCoreFullReports = 0;
            _successfullyGeneratedCoreSummaryReports = 0;

            while (true)
            {
                var dataSourceObjects = reportDataProvider.GetCompanyReportData_Core();

                if (dataSourceObjects.Length <= 0) break;

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("{0} companies done, next batch of {1} companies retrieved", _reportsCoreFullCount, dataSourceObjects.Length);
                Console.ResetColor();

                foreach (var ds in dataSourceObjects)
                {
                    target.SendAsync(ds).Wait();

                    var clone = ds.Clone();
                    clone.ReportType = ReportType.SummaryCoreRiskRatingsReport;
                    target.SendAsync(clone).Wait();
                    _reportsCoreSummaryCount++;
                }
                _reportsCoreFullCount += dataSourceObjects.Length;
            }
        }

        public bool GeneratePdfReport(Guid companyId, bool isSummaryReport, bool isCoreCompany = false)
        {
            var tokenSource = new CancellationTokenSource();

            var reportDataProvider = new ReportDataProvider(_bulkSize, tokenSource.Token, _dwConnectionString, _inputFile);

            var dataSourceObject = reportDataProvider.GetCompanyReportData(companyId, isSummaryReport, isCoreCompany);

            var result = GenerateCompanyReport(dataSourceObject[0]);
            if (isCoreCompany)
            {
                if (isSummaryReport)
                    _reportsCoreSummaryCount++;
                else
                    _reportsCoreFullCount++;
            }
            else
            {
                if (isSummaryReport)
                    _reportsSummaryCount++;
                else
                    _reportsFullCount++;
            }
            return result;
        }
                            
        public void GenerateBulkPdfReports()
        {
            _tokenSource = new CancellationTokenSource();

            RemovePreviousCompanyReportPdfs(false);

            Progress = 0.0;

            var processorCount = Environment.ProcessorCount;
            var maxDegreeOfParallelism = (int)Math.Floor(processorCount * 0.75);
            var boundedCapacity = processorCount*2;

            var bufferBlock = new BufferBlock<ReportDataSource>(new DataflowBlockOptions
                {
                    BoundedCapacity = boundedCapacity,
                    CancellationToken = _tokenSource.Token
                });

            var generator = new ActionBlock<ReportDataSource>(dataSource => GenerateCompanyReport(dataSource),
                new ExecutionDataflowBlockOptions
                {
                    BoundedCapacity = boundedCapacity,
                    MaxDegreeOfParallelism = maxDegreeOfParallelism,
                    CancellationToken = _tokenSource.Token
                });

            bufferBlock.LinkTo(generator, new DataflowLinkOptions { PropagateCompletion = true });
            bufferBlock.Completion.ContinueWith(task =>
            {
                Console.WriteLine("The status of the completion task is '{0}'.", task.Status);
            });


            LoadCompanyReportData(bufferBlock);

            bufferBlock.Complete();
            generator.Completion.Wait();

            Progress = 1;
        }

        public void GenerateBulkPdfReports_Core()
        {
            _tokenSource = new CancellationTokenSource();

            RemovePreviousCompanyReportPdfs(true);

            Progress = 0.0;

            var processorCount = Environment.ProcessorCount;
            var maxDegreeOfParallelism = (int)Math.Floor(processorCount * 0.75);
            var boundedCapacity = processorCount * 2;

            var bufferBlock = new BufferBlock<ReportDataSource>(new DataflowBlockOptions
            {
                BoundedCapacity = boundedCapacity,
                CancellationToken = _tokenSource.Token
            });

            var generator = new ActionBlock<ReportDataSource>(dataSource => GenerateCompanyReport(dataSource),
                new ExecutionDataflowBlockOptions
                {
                    BoundedCapacity = boundedCapacity,
                    MaxDegreeOfParallelism = maxDegreeOfParallelism,
                    CancellationToken = _tokenSource.Token
                });

            bufferBlock.LinkTo(generator, new DataflowLinkOptions { PropagateCompletion = true });
            bufferBlock.Completion.ContinueWith(task =>
            {
                Console.WriteLine("The status of the completion task is '{0}'.", task.Status);
            });


            LoadCompanyReportData_Core(bufferBlock);

            bufferBlock.Complete();
            generator.Completion.Wait();

            Progress = 1;
        }


        private void RemovePreviousCompanyReportPdfs(bool isCore)
        {
            try
            {
                var sw = Stopwatch.StartNew();
                using (var s3Client = new S3Client(_s3ClientSettings))
                {
                    var folderToDelete = string.Concat(isCore ? ReportType.CoreRiskRatingsReport : ReportType.RiskRatingsReport, "_" + _databaseGroup);
                    _log.Enqueue($"S3 Cleanup folder {folderToDelete}");
                    s3Client.DeleteBulk(_bucketName, folderToDelete);
                }
                sw.Stop();
                _log.Enqueue($"Removing of company reports {sw.ElapsedMilliseconds / 1000} [sec]");
            }
            catch (Exception ex)
            {
                _log.Enqueue($"S3 Cleanup exception: {ex.Message}. Stack trace {ex.StackTrace}");
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"S3 Cleanup exception: {ex.Message}. Stack trace {ex.StackTrace}");
                Console.ResetColor();
            }
        }

    }
}
